export const description = `
Execution Tests for the bitwise binary expression operations
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { assert } from '../../../../../common/util/util.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
import {
  abstractIntBits,
  i32Bits,
  ScalarValue,
  scalarType,
  u32Bits,
} from '../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../expression.js';

import { abstractIntBinary, binary, compoundBinary } from './binary.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

/**
 * Collection of functions and values required to implement bitwise tests for a
 * specific scalar type
 */
interface ScalarImpl {
  // builder is a mostly a wrapper around type builders like 'i32Bits' that
  // handles the (number|bigint) type check.
  builder: (bits: bigint | number) => ScalarValue;
  size: 32 | 64;
}

const kScalarImpls = {
  i32: {
    builder: (bits: bigint | number): ScalarValue => {
      assert(typeof bits === 'number');
      return i32Bits(bits);
    },
    size: 32,
  } as ScalarImpl,
  u32: {
    builder: (bits: bigint | number): ScalarValue => {
      assert(typeof bits === 'number');
      return u32Bits(bits);
    },
    size: 32,
  } as ScalarImpl,
  'abstract-int': {
    builder: (bits: bigint | number): ScalarValue => {
      assert(typeof bits === 'bigint');
      return abstractIntBits(bits);
    },
    size: 64,
  } as ScalarImpl,
};

/** Wrapper for converting from input type strings to the appropriate implementation */
function scalarImplForInputType(inputType: string): ScalarImpl {
  assert(inputType === 'i32' || inputType === 'u32' || inputType === 'abstract-int');
  return kScalarImpls[inputType];
}

/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
const kBitwiseOrStaticPatterns = {
  32: [
    {
      input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b01010010001001010010001001010010, 0b10100100010010100100010010100100],
      expected: 0b11110110011011110110011011110110,
    },
  ],
  64: [
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b1010010001001010010001001010010010100100010010100100010010100100n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b0101001000100101001000100101001010100100010010100100010010100100n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b1111011001101111011001101111011010100100010010100100010010100100n,
    },
  ],
};

/** @returns a set of bitwise-or cases for the specific input type */
function makeBitwiseOrCases(inputType: string) {
  const impl = scalarImplForInputType(inputType);
  const indices =
    impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];

  return [
    ...kBitwiseOrStaticPatterns[impl.size].map(c => {
      return {
        input: c.input.map(impl.builder),
        expected: impl.builder(c.expected),
      };
    }),
    // Permute all combinations of a single bit being set for the LHS and RHS
    ...indices.flatMap(i => {
      const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
      return indices.map(j => {
        const rhs = typeof j === 'bigint' ? 1n << j : 1 << j;
        assert(typeof lhs === typeof rhs);
        const result = typeof lhs === 'bigint' ? lhs | (rhs as bigint) : lhs | (rhs as number);
        return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
      });
    }),
  ];
}

g.test('bitwise_or')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 | e2: T
T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>

Bitwise-or. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32', 'abstract-int'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    t.skipIf(
      t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
    );
    const type = scalarType(t.params.type);
    const cases = makeBitwiseOrCases(t.params.type);
    const builder = t.params.type === 'abstract-int' ? abstractIntBinary('|') : binary('|');
    await run(t, builder, [type, type], type, t.params, cases);
  });

g.test('bitwise_or_compound')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 |= e2: T
T is i32, u32, vecN<i32>, or vecN<u32>

Bitwise-or. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    const type = scalarType(t.params.type);
    const cases = makeBitwiseOrCases(t.params.type);

    await run(t, compoundBinary('|='), [type, type], type, t.params, cases);
  });

/** Manually calculated bitwise-and cases used a check that the CTS test is correct */
const kBitwiseAndStaticPatterns = {
  32: [
    {
      input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
      expected: 0b01010010001001010010001001010010,
    },
  ],
  64: [
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b1010010001001010010001001010010010100100010010100100010010100100n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1010010001001010010001001010010010100100010010100100010010100100n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b0101001000100101001000100101001001010010001001010010001001010010n,
        0b0101101110110101101110110101101101011011101101011011101101011011n,
      ],
      expected: 0b0101001000100101001000100101001001010010001001010010001001010010n,
    },
  ],
};

/** @returns a set of bitwise-or cases for the specific input type */
function makeBitwiseAndCases(inputType: string) {
  const impl = scalarImplForInputType(inputType);
  const indices =
    impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];

  return [
    ...kBitwiseAndStaticPatterns[impl.size].map(c => {
      return {
        input: c.input.map(impl.builder),
        expected: impl.builder(c.expected),
      };
    }),
    // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
    ...indices.flatMap(i => {
      const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
      return indices.map(j => {
        const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
        assert(typeof lhs === typeof rhs);
        const result = typeof lhs === 'bigint' ? lhs & (rhs as bigint) : lhs & (rhs as number);
        return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
      });
    }),
  ];
}

g.test('bitwise_and')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 & e2: T
T is i32, u32, AbstractInt, vecN<i32>, vecN<u32>, or vecN<AbstractInt>

Bitwise-and. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32', 'abstract-int'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    t.skipIf(
      t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
    );
    const type = scalarType(t.params.type);
    const cases = makeBitwiseAndCases(t.params.type);
    const builder = t.params.type === 'abstract-int' ? abstractIntBinary('&') : binary('&');
    await run(t, builder, [type, type], type, t.params, cases);
  });

g.test('bitwise_and_compound')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 &= e2: T
T is i32, u32, vecN<i32>, or vecN<u32>

Bitwise-and. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    const type = scalarType(t.params.type);
    const cases = makeBitwiseAndCases(t.params.type);
    await run(t, compoundBinary('&='), [type, type], type, t.params, cases);
  });

/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
const kBitwiseExclusiveOrStaticPatterns = {
  32: [
    {
      input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
      expected: 0b11111111111111111111111111111111,
    },
    {
      input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
      expected: 0b00000000000000000000000000000000,
    },
    {
      input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
      expected: 0b01011011101101011011101101011011,
    },
    {
      input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
      expected: 0b10100100010010100100010010100100,
    },
    {
      input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
      expected: 0b01011011101101011011101101011011,
    },
    {
      input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
      expected: 0b00001001100100001001100100001001,
    },
  ],
  64: [
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
    },
    {
      input: [
        0b1010010001001010010001001010010010100100010010100100010010100100n,
        0b0000000000000000000000000000000000000000000000000000000000000000n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b1010010001001010010001001010010010100100010010100100010010100100n,
        0b1111111111111111111111111111111111111111111111111111111111111111n,
      ],
      expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
    },
    {
      input: [
        0b0000000000000000000000000000000000000000000000000000000000000000n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
    },
    {
      input: [
        0b1111111111111111111111111111111111111111111111111111111111111111n,
        0b1010010001001010010001001010010010100100010010100100010010100100n,
      ],
      expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
    },
    {
      input: [
        0b0101001000100101001000100101001001010010001001010010001001010010n,
        0b0101101110110101101110110101101101011011101101011011101101011011n,
      ],
      expected: 0b0000100110010000100110010000100100001001100100001001100100001001n,
    },
  ],
};

/** @returns a set of bitwise-xor cases for the specific input type */
function makeBitwiseExclusiveOrCases(inputType: string) {
  const impl = scalarImplForInputType(inputType);
  const indices =
    impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];

  return [
    ...kBitwiseExclusiveOrStaticPatterns[impl.size].map(c => {
      return {
        input: c.input.map(impl.builder),
        expected: impl.builder(c.expected),
      };
    }),
    // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
    ...indices.flatMap(i => {
      const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
      return indices.map(j => {
        const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
        assert(typeof lhs === typeof rhs);
        const result = typeof lhs === 'bigint' ? lhs ^ (rhs as bigint) : lhs ^ (rhs as number);
        return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
      });
    }),
  ];
}

g.test('bitwise_exclusive_or')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 ^ e2: T
T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>

Bitwise-exclusive-or. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32', 'abstract-int'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    t.skipIf(
      t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
    );
    const type = scalarType(t.params.type);
    const cases = makeBitwiseExclusiveOrCases(t.params.type);
    const builder = t.params.type === 'abstract-int' ? abstractIntBinary('^') : binary('^');
    await run(t, builder, [type, type], type, t.params, cases);
  });

g.test('bitwise_exclusive_or_compound')
  .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
  .desc(
    `
e1 ^= e2: T
T is i32, u32, vecN<i32>, or vecN<u32>

Bitwise-exclusive-or. Component-wise when T is a vector.
`
  )
  .params(u =>
    u
      .combine('type', ['i32', 'u32'] as const)
      .combine('inputSource', allInputSources)
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(async t => {
    const type = scalarType(t.params.type);
    const cases = makeBitwiseExclusiveOrCases(t.params.type);
    await run(t, compoundBinary('^='), [type, type], type, t.params, cases);
  });
